﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Xml;
using System.Xml.Linq;
using System.Xml.XPath;
using UtilZ.Dotnet.Ex.Base;

namespace UtilZ.Dotnet.Ex.Config
{
    /// <summary>
    /// 配置读写类
    /// </summary>
    internal partial class ConfigCore
    {

        #region Read
        /// <summary>
        /// 从xml配置中读取配置到指定的配置对象中
        /// </summary>
        /// <param name="xdoc">xml配置</param>
        /// <param name="config">指定的配置对象</param>
        public void ReadConfigFromXDocument(XDocument xdoc, ref object config)
        {
            ConfigAttributeTypes configAttributeTypes = new ConfigAttributeTypes();
            Type configType = config.GetType();
            ConfigRootAttribute configRootAttribute = ConfigRootAttribute.GetRootConfigRootAttribute(configType, configAttributeTypes);
            XElement rootEle = xdoc.XPathSelectElement(configRootAttribute.GetName(configType));
            if (rootEle == null)
            {
                return;
            }

            PropertyInfo[] propertyInfoArr = this.GetTypePropertyInfos(configType);
            this.ReadConfigToXml(rootEle, propertyInfoArr, ref config, configAttributeTypes);
        }




        private static Dictionary<string, XElement> GetElementsDic(XElement ownerEle)
        {
            var eleDic = new Dictionary<string, XElement>();
            foreach (var ele in ownerEle.Elements())
            {
                eleDic[ele.Name.LocalName] = ele;
            }

            return eleDic;
        }

        private void ReadConfigToXml(XElement ownerEle, PropertyInfo[] propertyInfoArr, ref object ownerObj, ConfigAttributeTypes configAttributeTypes)
        {
            ConfigAttribute attri;
            Dictionary<string, XElement> eleDic = GetElementsDic(ownerEle);
            TypeCategorys configDataType;
            string name, eleName;
            object customerValue;
            XElement ele;
            Type eleType;

            foreach (var propertyInfo in propertyInfoArr)
            {
                attri = propertyInfo.GetCustomAttribute(configAttributeTypes.IgnoreAttributeType, false) as ConfigAttribute;
                if (attri != null)
                {
                    //忽略该属性
                    continue;
                }

                attri = propertyInfo.GetCustomAttribute(configAttributeTypes.ConfigCommentAttributeType, false) as ConfigCommentAttribute;
                if (attri != null)
                {
                    //注释
                    continue;
                }

                configDataType = this.GetDataType(propertyInfo.PropertyType);
                attri = propertyInfo.GetCustomAttribute(configAttributeTypes.CustomerAttributeType, false) as ConfigAttribute;
                if (attri != null)
                {
                    var configItemCustomerAttribute = (ConfigCustomerAttribute)attri;
                    customerValue = configItemCustomerAttribute.CustomerConfig.Read(propertyInfo, ownerEle, configItemCustomerAttribute);
                    propertyInfo.SetValue(ownerObj, customerValue, null);
                    continue;
                }

                attri = propertyInfo.GetCustomAttribute(configAttributeTypes.CollectionAttributeType, false) as ConfigAttribute;
                if (attri != null)
                {
                    var collectionAtt = (ConfigCollectionAttribute)attri;
                    name = collectionAtt.GetName(propertyInfo);
                    if (!eleDic.TryGetValue(name, out ele))
                    {
                        //属性对应的配置项不存在,忽略
                        continue;
                    }

                    if (configDataType == TypeCategorys.IDictionary)
                    {
                        eleName = this.GetIDictionaryElementName(collectionAtt.ElementName);
                        this.ReadIDictionary(ref ownerObj, ele.Elements(eleName).ToArray(), propertyInfo, configAttributeTypes);
                    }
                    else
                    {
                        eleType = this.GetIListElementType(propertyInfo.PropertyType);
                        eleName = collectionAtt.GetElementName(eleType);
                        this.ReadIList(ref ownerObj, propertyInfo, ele.Elements(eleName).ToArray(), configAttributeTypes);
                    }
                    continue;
                }

                attri = propertyInfo.GetCustomAttribute(configAttributeTypes.ObjectAttributeType, false) as ConfigAttribute;
                if (attri != null)
                {
                    //复合对象
                    name = attri.GetName(propertyInfo);
                    if (!eleDic.TryGetValue(name, out ele))
                    {
                        //属性对应的配置项不存在,忽略
                        continue;
                    }

                    this.ReadObject(ele, configAttributeTypes, propertyInfo, ref ownerObj);
                    continue;
                }

                attri = propertyInfo.GetCustomAttribute(configAttributeTypes.ItemAttributeType, false) as ConfigAttribute;
                if (attri != null)
                {
                    //基元项
                    name = attri.GetName(propertyInfo);
                    if (!eleDic.TryGetValue(name, out ele))
                    {
                        //属性对应的配置项不存在,忽略
                        continue;
                    }

                    ReadItem(ele, ((ConfigItemAttribute)attri).GetConverter(propertyInfo), propertyInfo, ownerObj);
                    continue;
                }


                //未标记项====================================================================================================


                switch (configDataType)
                {
                    case TypeCategorys.IDictionary:
                        name = this.GetIDictionaryName(null, propertyInfo);
                        if (!eleDic.TryGetValue(name, out ele))
                        {
                            //属性对应的配置项不存在,忽略
                            continue;
                        }

                        eleName = this.GetIDictionaryElementName(null);
                        this.ReadIDictionary(ref ownerObj, ele.Elements(eleName).ToArray(), propertyInfo, configAttributeTypes);
                        break;
                    case TypeCategorys.IList:
                        eleName = this.GetConfigItemName(propertyInfo);
                        if (!eleDic.TryGetValue(eleName, out ele))
                        {
                            //属性对应的配置项不存在,忽略
                            continue;
                        }

                        eleType = this.GetIListElementType(propertyInfo.PropertyType);
                        eleName = this.GetIListElementName(eleType);
                        this.ReadIList(ref ownerObj, propertyInfo, ele.Elements(eleName).ToArray(), configAttributeTypes);
                        break;
                    case TypeCategorys.Object:
                        string complexEleName = this.GetConfigItemName(propertyInfo);
                        if (!eleDic.TryGetValue(complexEleName, out ele))
                        {
                            //属性对应的配置项不存在,忽略
                            continue;
                        }

                        this.ReadObject(ele, configAttributeTypes, propertyInfo, ref ownerObj);
                        break;
                    case TypeCategorys.Basic:
                        eleName = this.GetConfigItemName(propertyInfo);
                        if (!eleDic.TryGetValue(eleName, out ele))
                        {
                            //属性对应的配置项不存在,忽略
                            continue;
                        }

                        ReadItem(ele, null, propertyInfo, ownerObj);
                        break;
                    default:
                        throw new NotImplementedException(configDataType.ToString());
                }
            }
        }

        private void ReadIDictionary(ref object ownerObj, XElement[] eleEleArr, PropertyInfo propertyInfo, ConfigAttributeTypes configAttributeTypes)
        {
            var dic = (IDictionary)propertyInfo.GetValue(ownerObj, null);
            if (dic == null)
            {
                if (!propertyInfo.CanWrite)
                {
                    throw new ArgumentException($"属性{propertyInfo.DeclaringType.FullName}.{propertyInfo.Name}不支持set操作");
                }

                dic = ActivatorEx.CreateInstance<IDictionary>(propertyInfo.PropertyType);
                propertyInfo.SetValue(ownerObj, dic, null);
            }

            this.ReadIDictionary(dic, eleEleArr, propertyInfo.PropertyType, configAttributeTypes);
        }

        private void ReadIDictionary(IDictionary dic, XElement[] eleEleArr, Type eleType, ConfigAttributeTypes configAttributeTypes)
        {
            XElement keyEle, valueEle;
            string keyStr, valueStr;
            object key, value = null;
            Type[] argsTypeArr = eleType.GetGenericArguments();
            TypeCategorys configDataType = this.GetDataType(argsTypeArr[1]);
            string name2 = null, eleName2 = null, eleName3 = null;
            Type eleType2 = null;
            PropertyInfo[] objPropertyInfoArr = null;
            XElement[] eleEleArr2;

            foreach (var childEle in eleEleArr)
            {
                keyEle = childEle.Element(_KEY);
                keyStr = XmlEx.GetXElementAttributeValue(keyEle, _VALUE, false);
                key = StringToObject(keyStr, argsTypeArr[0]);

                valueEle = childEle.Element(_VALUE);
                switch (configDataType)
                {
                    case TypeCategorys.Basic:
                        valueStr = XmlEx.GetXElementAttributeValue(valueEle, _VALUE, true);
                        if (valueStr == null)
                        {
                            value = null;
                        }
                        else
                        {
                            value = StringToObject(valueStr, argsTypeArr[1]);
                        }
                        break;
                    case TypeCategorys.IDictionary:
                        if (name2 == null)
                        {
                            name2 = this.GetIListElementName(argsTypeArr[1]);
                        }

                        if (eleName2 == null)
                        {
                            eleName2 = this.GetIDictionaryElementName(null);
                        }

                        value = Activator.CreateInstance(argsTypeArr[1]);
                        this.ReadIDictionary((IDictionary)value, valueEle.XPathSelectElements($"{name2}/{eleName2}").ToArray(), argsTypeArr[1], configAttributeTypes);
                        break;
                    case TypeCategorys.IList:
                        if (eleType2 == null)
                        {
                            eleType2 = this.GetIListElementType(argsTypeArr[1]);
                        }

                        if (eleName2 == null)
                        {
                            eleName2 = this.GetIListElementName(argsTypeArr[1]);
                        }

                        if (eleName3 == null)
                        {
                            eleName3 = this.GetIListElementName(eleType2);
                        }

                        eleEleArr2 = valueEle.XPathSelectElements($"{eleName2}/{eleName3}").ToArray();
                        value = this.CreateIList(argsTypeArr[1], eleEleArr2.Length);
                        this.ReadIList((IList)value, eleType2, eleEleArr2, configAttributeTypes);
                        break;
                    case TypeCategorys.Object:
                        if (objPropertyInfoArr == null)
                        {
                            objPropertyInfoArr = this.GetTypePropertyInfos(argsTypeArr[1]);
                        }

                        if (eleName2 == null)
                        {
                            eleName2 = this.GetIListElementName(argsTypeArr[1]);
                        }

                        value = Activator.CreateInstance(argsTypeArr[1]);
                        this.ReadConfigToXml(valueEle.Element(eleName2), objPropertyInfoArr, ref value, configAttributeTypes);
                        break;
                    default:
                        throw new NotSupportedException(configDataType.ToString());
                }

                dic[key] = value;
            }
        }



        private void ReadIList(ref object ownerObj, PropertyInfo propertyInfo, XElement[] eleEleArr, ConfigAttributeTypes configAttributeTypes)
        {
            //非字典集合类型
            var list = (IList)propertyInfo.GetValue(ownerObj, null);
            if (list == null)
            {
                if (!propertyInfo.CanWrite)
                {
                    throw new ArgumentException($"属性{propertyInfo.DeclaringType.FullName}.{propertyInfo.Name}不支持set操作");
                }

                list = this.CreateIList(propertyInfo.PropertyType, eleEleArr.Length);
                propertyInfo.SetValue(ownerObj, list, null);
            }

            Type eleType = this.GetIListElementType(propertyInfo.PropertyType);
            this.ReadIList(list, eleType, eleEleArr, configAttributeTypes);
        }

        private void ReadIList(IList list, Type eleType, XElement[] eleEleArr, ConfigAttributeTypes configAttributeTypes)
        {
            TypeCategorys configDataType = this.GetDataType(eleType);
            string value, eleName = null;
            object obj;

            switch (configDataType)
            {
                case TypeCategorys.IDictionary:
                    foreach (var eleEle in eleEleArr)
                    {
                        obj = Activator.CreateInstance(eleType);
                        this.ReadIDictionary((IDictionary)obj, eleEle.Elements(eleName).ToArray(), eleType, configAttributeTypes);
                        list.Add(obj);
                    }
                    break;
                case TypeCategorys.IList:
                    var itemType2 = this.GetIListElementType(eleType);
                    eleName = this.GetIListElementName(itemType2);
                    XElement[] eleEleArr2;
                    foreach (var eleEle in eleEleArr)
                    {
                        eleEleArr2 = eleEle.Elements(eleName).ToArray();
                        obj = this.CreateIList(eleType, eleEleArr2.Length);
                        this.ReadIList((IList)obj, itemType2, eleEleArr2, configAttributeTypes);
                        list.Add(obj);
                    }
                    break;
                case TypeCategorys.Object:
                    PropertyInfo[] objPropertyInfoArr = this.GetTypePropertyInfos(eleType);
                    int index = 0;
                    foreach (var eleEle in eleEleArr)
                    {
                        obj = Activator.CreateInstance(eleType);
                        this.ReadConfigToXml(eleEle, objPropertyInfoArr, ref obj, configAttributeTypes);
                        if (list is Array)
                        {
                            list[index++] = obj;
                        }
                        else
                        {
                            list.Add(obj);
                        }
                    }
                    break;
                case TypeCategorys.Basic:
                    foreach (var eleEle in eleEleArr)
                    {
                        value = XmlEx.GetXElementAttributeValue(eleEle, _VALUE, true);
                        list.Add(StringToObject(value, eleType));
                    }
                    break;
                default:
                    throw new NotImplementedException(configDataType.ToString());
            }
        }

        private IList CreateIList(Type ilistType, int eleCount)
        {
            IList list;
            if (ilistType.IsArray)
            {
                Type eleType = this.GetIListElementType(ilistType);
                list = Array.CreateInstance(eleType, eleCount);
            }
            else
            {
                list = ActivatorEx.CreateInstance<IList>(ilistType);
            }

            return list;
        }


        private void ReadObject(XElement ele, ConfigAttributeTypes configAttributeTypes, PropertyInfo propertyInfo, ref object ownerObj)
        {
            object value = propertyInfo.GetValue(ownerObj, null);
            if (value == null)
            {
                if (!propertyInfo.CanWrite)
                {
                    throw new ArgumentException($"属性{propertyInfo.DeclaringType.FullName}.{propertyInfo.Name}不支持set操作");
                }

                value = Activator.CreateInstance(propertyInfo.PropertyType);
                if (!propertyInfo.PropertyType.IsValueType)
                {
                    propertyInfo.SetValue(ownerObj, value, null);
                }
            }

            PropertyInfo[] complexProInfoArr = this.GetTypePropertyInfos(propertyInfo.PropertyType);
            this.ReadConfigToXml(ele, complexProInfoArr, ref value, configAttributeTypes);

            if (propertyInfo.PropertyType.IsValueType)
            {
                propertyInfo.SetValue(ownerObj, value, null);
            }
        }


        private static void ReadItem(XElement ele, IConfigValueConverter converter, PropertyInfo propertyInfo, object ownerObj)
        {
            if (!propertyInfo.CanWrite)
            {
                throw new ArgumentException($"属性{propertyInfo.DeclaringType.FullName}.{propertyInfo.Name}不支持set操作");
            }

            object value;
            string valueStr = XmlEx.GetXElementAttributeValue(ele, _VALUE, true);
            if (valueStr == null)
            {
                value = null;
            }
            else
            {
                if (converter != null)
                {
                    value = converter.ConvertFrom(propertyInfo, valueStr);
                }
                else
                {
                    value = StringToObject(valueStr, propertyInfo.PropertyType);
                }
            }

            propertyInfo.SetValue(ownerObj, value, null);
        }


        private static object StringToObject(string str, Type targetType)
        {
            Type underlyingType = Nullable.GetUnderlyingType(targetType);
            if (underlyingType != null)
            {
                if (string.IsNullOrEmpty(str))
                {
                    return null;
                }
                else
                {
                    targetType = underlyingType;
                }
            }

            object value = str;
            if (Type.GetTypeCode(targetType) != TypeCode.String)
            {
                value = ConvertEx.ConvertToObject(targetType, str.Trim());
            }

            return value;
        }
        #endregion

    }
}
